/**************************************************************************
* FXSerial
* Copyright (C) 2010 thierry lorthiois (lorthiois@bbsoft.fr)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**************************************************************************/
#include <stdio.h>

#include "FXSerial.h"

#ifndef WIN32
#include <sstream>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define OCTET_MAX 255
#endif


FXSerial::FXSerial()
{
	ok=false;
}

FXSerial::~FXSerial()
{
	destroy();
}


bool FXSerial::create(const FXString &_port, int _bauds, int _parity, int _stopbit, int _databit, int _timeout)
{
	destroy();

	timeout = _timeout;

	// conversion du port linux en port windows (ttyS0 -> COM1)
	int num = FXIntVal(_port.right(1));
#ifdef WIN32
	num++;
	port = "\\\\.\\COM" + FXStringVal(num);
#else
	port = "/dev/ttyS" + FXStringVal(num);
#endif


#ifdef WIN32
	handle = CreateFile(port.text(), GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

	COMMTIMEOUTS timeout2;
	timeout2.ReadIntervalTimeout = timeout;
	timeout2.ReadTotalTimeoutMultiplier = 1;
	timeout2.ReadTotalTimeoutConstant  = timeout;
	timeout2.WriteTotalTimeoutMultiplier = 1;
	timeout2.WriteTotalTimeoutConstant  = timeout;

	if (handle == INVALID_HANDLE_VALUE) {
		printf("Unabled to open serial %s\n", port.text());
		return false;
	}
	DCB dcbPort;

	if (SetCommTimeouts(handle, &timeout2) && PurgeComm(handle, PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR) && GetCommState(handle, &dcbPort)) {
		dcbPort.BaudRate = _bauds;
		dcbPort.fParity = 1;
		dcbPort.Parity = _parity;
		dcbPort.StopBits = _stopbit;
		dcbPort.ByteSize = _databit;
		if (SetCommState(handle, &dcbPort)) {
			ok=true;
			return true;
		}
	}
#else
	handle = open(port.text(), O_RDWR|O_NOCTTY);
	if (handle < 0) {
		printf("Unabled to open serial %s\n", port.text());
		return false;
	}
	struct termios termios_serie;

	if (tcgetattr(handle, &termios_serie)==0) {
		// ignore break point
		termios_serie.c_iflag = IGNBRK;
		termios_serie.c_oflag = 0;
		termios_serie.c_lflag = 0;
		termios_serie.c_cflag = CLOCAL | CREAD;

		switch(_bauds) {
			case 1200:
			termios_serie.c_cflag |= B1200;
			break;
			case 2400:
			termios_serie.c_cflag |= B2400;
			break;
			case 4800:
			termios_serie.c_cflag |= B4800;
			break;
			case 9600:
			termios_serie.c_cflag |= B9600;
			break;
			case 19200:
			termios_serie.c_cflag |= B19200;
			break;
			case 38400:
			termios_serie.c_cflag |= B38400;
			break;
			case 57600:
			termios_serie.c_cflag |= B57600;
			break;
			case 115200:
			termios_serie.c_cflag |= B115200;
			break;
			case 921600:
			termios_serie.c_cflag |= B921600;
			break;
			default:
			termios_serie.c_cflag |= B9600;
		}

		if (_parity == ODDPARITY)
			termios_serie.c_cflag |= (PARENB | PARODD);
		else if (_parity == EVENPARITY)
			termios_serie.c_cflag |= PARENB;

		if (_stopbit == ONESTOPBIT)
			termios_serie.c_cflag |= CSTOPB;

		if (_databit == 8)
			termios_serie.c_cflag |= CS8;
		else
			termios_serie.c_cflag |= CS7;

		termios_serie.c_cc[VTIME] = 1;
		termios_serie.c_cc[VMIN] = OCTET_MAX;

		serie_timeout.tv_sec=(timeout/1000);
		serie_timeout.tv_usec=(timeout%1000)*1000;

		if (tcsetattr(handle, TCSANOW, &termios_serie)==0) {
			ok=true;
			return true;
		}
	}
#endif

	printf("Unabled to configure serial %s\n", port.text());
	destroy();
	return false;
}


bool FXSerial::send_data(const char *data, size_t data_len)
{
	if (!ok)
		return false;

#ifdef WIN32
	DWORD written,tobewritten;

	tobewritten=data_len;
	WriteFile(handle, data, tobewritten, &written, NULL);
	if (written!=tobewritten)
		return false;
#else
	if (write(handle, data, data_len)!=(int)data_len)
		return false;
#endif

	return true;
}


int FXSerial::recv_data(char *data, size_t data_len)
{
	if (!ok)
		return -1;

#ifdef WIN32
	DWORD read,toberead;

	toberead=data_len;
	ReadFile(handle, data, toberead, &read, NULL);

	return (int)read;
#else
	fd_set check;
	FD_ZERO(&check);
	FD_SET(handle, &check);

	struct timeval check_timeout;
	memcpy(&check_timeout,&serie_timeout,sizeof(struct timeval));

	if (!select(handle+1,&check,NULL,NULL,&check_timeout))
		return -1;

	// receive with maximum OCTET_MAX char
	for (size_t i=0;i<data_len;i+=OCTET_MAX) {
		size_t nb_data_capt=data_len-i;
		if (nb_data_capt>OCTET_MAX)
			nb_data_capt=OCTET_MAX;

		if (read(handle, data+i, nb_data_capt)!=(int)nb_data_capt)
			return (int)(i+nb_data_capt);
	}

	return (int)data_len;
#endif
}


void FXSerial::destroy()
{
	if (ok) {
		clean_buffer();
#ifdef WIN32
		CloseHandle(handle);
#else
		close(handle);
#endif
	}
	ok=false;
}


bool FXSerial::isok()
{
	return ok;
}


bool FXSerial::clean_buffer()
{
	if (!ok)
		return false;

#ifdef WIN32
	if (!PurgeComm(handle, PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR))
		return false;
#else
	tcflush(handle, TCOFLUSH);
	tcflush(handle, TCIFLUSH);
#endif

	return true;
}


void FXSerial::setRTS(bool state)
{
#ifdef WIN32
	DCB dcbPort;

	if (GetCommState(handle, &dcbPort)) {
		if (state)
			dcbPort.fRtsControl = RTS_CONTROL_ENABLE;
		else
			dcbPort.fRtsControl = RTS_CONTROL_DISABLE;
		SetCommState(handle, &dcbPort);
	}

#else
	int bits, last;
	if (ioctl(handle, TIOCMGET, &bits) == -1)
		printf("FXSerial : ioctl failed\n");

	if (state)
		bits |= TIOCM_RTS;
	else
		bits &= ~TIOCM_RTS;

	ioctl(handle, TIOCMSET, &bits);
	usleep(20000L);
#endif
}


bool FXSerial::receiveLine(char *buffer, int size)
{
	unsigned char checksum, checksumReceive;
	int  nbCar, cpt;
	char temp[2];

	size++;
	cpt = 0;

beginReceiveLine:
	// receive line
	cpt++;

	nbCar = recv_data(buffer, size);
	if (nbCar < 0 || nbCar != size) {
		printf("receiveLine : timeout (%d)\n", nbCar);
		goto errorReceiveLine;
	}

	checksumReceive = buffer[size-1];
	buffer[size-1] = 0;

	// calculate checksum (on 7 bits)
	checksum = 0;
	for ( int i=0; i < (size-1) ; i++ )
		checksum += buffer[i];
	checksum &= 0x7f;
	if ( checksum == 0 )
		checksum = 1;

   // check acquittement
   if (checksum != checksumReceive) {
		printf("receiveLine : bad checksum (%d,%d)\n", checksum, checksumReceive);
		goto errorReceiveLine;
	}

	// sending ack (the checksum)
	temp[0] = checksumReceive;
	if ( !send_data(temp, 1) ) {
		printf("receiveLine : unabled to send ack\n");
		goto errorReceiveLine;
	}
	return true;

errorReceiveLine:
	printf("  '%s' %d\n", buffer, nbCar);
	if ( cpt > 3 ) 
		return false;
	else 
		goto beginReceiveLine;
}


bool FXSerial::sendLine(char *buffer, int size)
{
	unsigned char checksum, ack;
	int  nbCar, cpt;
	char temp[2];

	// calculate checksum and insert in 'buffer'
	size++;
	checksum = 0;
	for ( int i = 0; i < (size-1) ; i++ )
		checksum += buffer[i];
	checksum &= 0x7f;
	if ( checksum == 0 )
		checksum = 1;
	buffer[size-1] = checksum;
	buffer[size] = 0;

	cpt = 0;

beginSendLine:
   // send
   cpt++;

	if (!send_data(buffer, size)) {
		printf("sendLine : timeout\n");
		goto errorSendLine;
	}

	// wait for ack (checksum)
	nbCar = recv_data(temp, 1);
	if (nbCar < 0 || nbCar != 1) {
		printf("sendLine : missing ack %d\n", nbCar);
		goto errorSendLine;
	}
	if (temp[0] != checksum) {
		printf("sendLine : bad ack\n");
		goto errorSendLine;
	}
   return true;

errorSendLine:
   if ( cpt > 3 ) {
      return false;
   }
   else {
      goto beginSendLine;
   }
}


